70 行代码在 Vue3 中实现网格布局拖放排序


简介

产品新增了一个拖拽排序的需求,我本来已经找到了 Vue Fraggable 这个库,但是这库不支持 Vue3,很遗憾,只有自己造轮子了。

很早就知道 H5 拖拽相关的 API 了,但是从来没用过。项目里其他东西都还多,只能到网上找现成的了,现学习怕来不及。

很幸运找到了一个掘金大佬 一尾流莺 写的一篇 【实战技巧】VUE3.0 实现简易的可拖放列表排序,照着葫芦画瓢,写了一个简要版的 70 行代码的拖拽功能,勉强凑合着用。

上代码

先说下环境,项目是 Vue-cli 创建的 Vue3 项目,包含以下主要依赖库:

  • Vue3
  • typescript

代码是 SFC 的 setup 模式:

<script setup lang="ts">
  import { reactive, ref, TransitionGroup } from "vue";

  // 模拟一个列表
  const drags = ref([1, 2, 3, 4, 5, 6]);

  // 拖拽时新旧位置的索引
  const dragIndex = reactive({
    old: -1,
    new: -1,
  });

  // 开始拖拽,记录被拖拽的节点索引
  function onDragstart(index: number) {
    dragIndex.old = index;
  }

  // 拖拽到有效目标上时,记录有效目标的索引
  function onDragover(index: number) {
    dragIndex.new = index;
  }

  // 进行放置的事件
  function onDrop() {
    // 仅当拖放到新的位置时,切换新旧节点位置
    if (dragIndex.old !== dragIndex.new) {
      const changeItem = drags.value.splice(dragIndex.old, 1)[0];
      drags.value.splice(dragIndex.new, 0, changeItem);
    }
  }
</script>
<template>
  <!-- 增加过渡特效 -->
  <TransitionGroup class="root" name="flip-list" tag="div">
    <div
      v-for="(item, i) in drags"
      :key="item"
      class="child"
      draggable="true"
      @dragstart="onDragstart(i)"
      @dragover.prevent="onDragover(i)"
      @drop="onDrop"
    >
      {{ item }}
    </div>
  </TransitionGroup>
</template>
<style scoped>
  .root {
    margin: 10px;
    padding: 10px;
    border: 3px solid #eee;
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(auto-fill, 200px);
  }

  .child {
    height: 200px;
    font-size: 100px;
    background-color: #eee;
    cursor: move;
  }

  .flip-list-move {
    transition: transform 0.8s ease;
  }
</style>

如果你没装 typescript,那么把 ts 的相关注释取消掉,<script> 元素里的 lang="ts" 也删掉就行。

附加解释

依赖事件

拖放排序主要依赖以下三个事件:

@dragstart="onDragstart(i)"
@dragover.prevent="onDragover(i)"
@drop="onDrop"
  • dragstart:会在你开始拖设置 draggable="true" 属性的元素时触发,我们记录下它在列表中的唯一值,Vue3 里面很方便,直接用索引就行,好排序。

  • dragover.prevent:会在你把元素拖拽到一个有效的放置目标上时,这个事件在可被放置元素的节点上触发。此时我们记录下这个节点的索引。另外,如果不取消默认行为,是不会触发放置事件 drop 的,所以我们要取消默认行为。

  • drop:当一个元素或是选中的文字被拖拽释放到一个有效的释放目标位置时,drop 事件被抛出。此时我们来比对下之前记录的索引号,如果发现更改了位置,进行替换即可。

过渡

Vue 最牛逼的我觉得就是列表过渡了,代码里使用 TransitionGroup 直接进行列表过渡,使得拖拽更加流畅。

结尾

只是写皮毛,抽时间还是要好好的研究下拖拽的其他事件,还有原生实现,就这套逻辑我都没在 React 中测试过,不知道能不能行得通。

另外拖拽还有很多需要优化的地方,这里只是个雏形,不过项目开发已经完全够用了。

参考